/**
 * @file vfork1.c
 * @author liangwenhao (1943080020@qq.com)
 * @brief 
 * @version 0.1
 * @date 2021-07-23
 * 
 * @copyright Copyright (c) 2021
 * 
 * @authorGitee  https://gitee.com/liangwenhao
 * @authorGithub https://github.com/WHaoL
 * @authorBlogs https://blog.csdn.net/liangwenhao1108
 *  
 * @Code Description: 测试vfork()   P187-188
 *                    vfork()一般和exec一起使用
 */

#include "apue.h"

int globvar = 6; //外部变量，并初始化

int main()
{
    int var; //自动变量，存储在stack
    pid_t pid;

    var = 88;

    //printf()是标准I/O库函数，是带缓冲区的
    //当输出到终端时是行缓冲，当输出到文件是全缓冲。此处我们没有主动冲刷缓冲区
    printf("before fork\n");

    if ((pid = vfork()) < 0)
        err_sys("fork error");
    else if (pid == 0)
    {
        //child
        globvar++;
        var++;
        _exit(0); //_exit()和_Exit()立即进入内核，
        // exit(0); //执行清理工作后，然后调用exit()或__Exit()

        //exit()是标准库函数，但是它现在只冲刷缓冲区，并不关闭流，所以此时调用exit()和调用_exit()输出显示是一样的。

        //exit()的旧版本，不仅冲刷缓冲区 而且关闭流，这会导致父进程没有输出，
        //  因为使用了vfork()之后，子进程调用exit或exec之前，子进程是运行在父进程的空间中，此时子进程的操作会改变父进程的数据
    }

    //父进程
    //vfork()是：子进程先执行，父进程阻塞 直到子进程调用exit/exec
    printf("pid = %ld, glob = %d, var = %d\n", (long)getpid(), globvar, var);
    exit(0); //return 0;
}

/*

作者：<em>r00tk1t</em>
链接：https://www.zhihu.com/question/304323673/answer/543471196
来源：知乎
著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。

fork和vfork语义上是不同的，fork对父子进程的调度取决于调度器，vfork则父进程阻塞直到子进程调用exit/exec（所以某应用场景希望这一同步就可以用vfork，但我好像从来没见过）。
我个人的理解是vfork一般都是配合exec的，早期vfork的诞生可能是因为Linux在不支持COW的时代，fork的成本太高，所以vfork+exec显然比fork+exec更好。
但随着COW的引入，fork实际上也不会一开始就copy数据段、堆、栈区了（代码段则因为天然的内存映射机制，只是拷贝了线性区结构而父子进程共用实际的物理内存页），而是写时复制。
所以，对现阶段来说，这一点没什么差别了。
但fork和vfork的另一个重要差别在于前者生成的是进程，而后者是一个轻量级进程。

fork和vfork最终都是调用do_fork系统调用，差别在于传入的flag不同（还有个clone，可以diy flags，但fork和vfork的flags已经设置好了），
fork时clone_flag = SIGCHLD，vfork时clone_flag = CLONE_VM | CLONEVFORK | SIGCHLD，其中CLONE_VM决定了vfork创建的是LWP，
现在的Linux内核都支持LWP->内核线程一对一的绑定关系，即轻量级进程的调度也依赖于内核调度器。
实际上Linux内核弱化了进程、线程的概念（不像Windows进程严格管理线程，调度单位只能是线程，弱化了进程父子关系，使得进程看起来可以通过CreateProcess凭空捏一个出来），
内核都是task_struct统一管理，差别就在于轻量级进程有这个额外标志，与其他进程共享线性地址空间。
另一个CLONEVFORK标志在do_fork的内部实现了对父进程上锁，所以才能实现vfork语义。 

另外关于Linux内核的进程和线程，实际上由于历史原因，Linux也遵守了真香定律。
以pthread的两种实现LinuxThread（Linux2.4以前）和NPTL（2.5以后）为例，
早期内核是没有TGID的，只有PID，所以内核根本就没有线程的概念，不管有没有CLONE_VM，在内核眼里都是进程，都是调度器调度的单元，
而这就给LinuxThread的实现带来了难题——LinuxThread采用1:1模型，即每个线程都是LWP对应一个内核线程（这个线程的概念是从我们的视角出发的），
在内核不支持的情况下如何实现线程的一套同步互斥呢？
LinuxThread使用信号来模拟，显然效率不高。
而NPTL时代，内核引入TGID，此时依然是1:1模型，但不是简单的LWP了，TGID把这些线程联系了起来，那如何告知内核呢？
答案就是NPTL创建线程时传递一个CLONE_THREAD 标志，所以用的是clone。
内核把TGID填写为调用者的PID，PID填写新线程号（原本的进程号）。
有了这一层联系，线程的同步互斥就可以依赖于用户空间的锁（因为大家共享同一片地址空间），省去了此前内核态切来切去的麻烦，效率自然高了，也就是所谓的futex。

*/